package com.kuaiyin.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.kuaiyin.dto.TestPlanCaseDTO;
import com.kuaiyin.dto.TestPlanDTO;
import com.kuaiyin.entity.TestCase;
import com.kuaiyin.entity.TestPlan;
import com.kuaiyin.entity.TestPlanCase;
import com.kuaiyin.enums.ExecStatus;
import com.kuaiyin.enums.Status;
import com.kuaiyin.enums.TestPlanExecMode;
import com.kuaiyin.exception.BaseException;
import com.kuaiyin.executor.QuartzManager;
import com.kuaiyin.executor.TestPlanExecutor;
import com.kuaiyin.mapper.RequestMapper;
import com.kuaiyin.mapper.TestCaseMapper;
import com.kuaiyin.mapper.TestPlanCaseMapper;
import com.kuaiyin.mapper.TestPlanMapper;
import com.kuaiyin.service.ITestPlanService;
import com.kuaiyin.utils.PageHelper;
import com.kuaiyin.utils.PageResult;
import com.kuaiyin.utils.RedisUtils;
import com.kuaiyin.vo.TestPlanCaseVO;
import com.kuaiyin.vo.TestPlanVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * <p>
 * 测试计划表 服务实现类
 * </p>
 *
 * @author ng
 * @since 2025-04-30
 */
@Slf4j
@Service
public class TestPlanServiceImpl extends ServiceImpl<TestPlanMapper, TestPlan> implements ITestPlanService {

    @Autowired
    private TestPlanMapper testPlanMapper;
    @Autowired
    private TestPlanCaseMapper testPlanCaseMapper;
    @Autowired
    private TestCaseMapper testCaseMapper;
    @Autowired
    private RequestMapper requestMapper;
    @Autowired
    private RedisUtils redisUtils;
    @Autowired
    @Lazy
    private QuartzManager quartzManager;

    // ===== Redis Key 前缀 =====
    private static final String TEST_PLAN_RUNNING_KEY = "test_plan:running:";
    private static final String REDIS_KEY_PATTERN = "testPlan_id_%d_testCase_id_%d";

    @Override
    @Transactional // 开启事务
    public void saveTestPlan(TestPlanDTO testPlanDTO) {
        //1、测试计划表插入数据
        List<TestPlanCaseDTO> cases = testPlanDTO.getCases(); // 关联的用例列表
        if (lambdaQuery().eq(TestPlan::getName, testPlanDTO.getName()).exists()) {
            throw BaseException.of(0, "计划名称已存在");
        }
        validateTestPlanDTO(testPlanDTO); // 调用统一校验方法

        TestPlan testPlan = new TestPlan();
        BeanUtils.copyProperties(testPlanDTO, testPlan);  //将dto对象拷贝到实体对象中
        testPlanMapper.insert(testPlan); // 测试计划表插入数据

        //3、添加定时任务
        if (testPlan.getScheduleConfig() != null && !testPlan.getScheduleConfig().isEmpty()) {
            quartzManager.addJob(testPlan.getId(), testPlan.getScheduleConfig());
        }

        //2、测试计划关联表插入数据
        for (TestPlanCaseDTO testPlanCaseDTO : cases) {
            //  关联用例表插入数据
            TestPlanCase testPlanCase = new TestPlanCase();
            BeanUtils.copyProperties(testPlanCaseDTO, testPlanCase);
            testPlanCase.setPlanId(testPlan.getId());  //  关联计划ID
            testPlanCaseMapper.insert(testPlanCase);
        }
    }

    @Override
    public void updateTestPlan(Long id, TestPlanDTO testPlanDTO) {
        testPlanDTO.setId(id);
        //0、 校验名称是否已存在
        if (lambdaQuery().eq(TestPlan::getName, testPlanDTO.getName()).ne(TestPlan::getId, id).exists()) {
            throw BaseException.of(0, "计划名称已存在");
        }
        // 1、复用统一校验逻辑
        validateTestPlanDTO(testPlanDTO);
        //2、更新测试计划表数据
        TestPlan testPlan = new TestPlan();
        BeanUtils.copyProperties(testPlanDTO, testPlan);
        testPlanMapper.updateById(testPlan);
        // 3、更新定时任务
        if (testPlan.getScheduleConfig() != null && !testPlan.getScheduleConfig().isEmpty()) {
            quartzManager.updateJob(id, testPlan.getScheduleConfig());
        } else {
            quartzManager.deleteJob(id);
        }

        // 删除旧的关联数据（可选）
        testPlanCaseMapper.deleteByPlanId(id);

        // 插入新的关联数据
        for (TestPlanCaseDTO dto : testPlanDTO.getCases()) {
            TestPlanCase testPlanCase = new TestPlanCase();
            BeanUtils.copyProperties(dto, testPlanCase);
            testPlanCase.setPlanId(id);  //  关联计划ID
            testPlanCaseMapper.insert(testPlanCase);
        }

    }

    @Override
    @Transactional
    public void removeById(Long id) {
        super.removeById(id); // 删除测试计划表数据
        testPlanCaseMapper.deleteByPlanId(id);  // 删除关联用例表数据
        // 3. 如果存在定时任务，一并删除
        quartzManager.deleteJob(id);
    }

    @Override
    public TestPlanVO getById(Long id) {
        // 1. 查询主表 test_plan 数据
        TestPlan testPlan = super.getById(id);
        // 2. 转换为主 VO 对象
        TestPlanVO testPlanVO = new TestPlanVO();
        BeanUtils.copyProperties(testPlan, testPlanVO);

        // 3. 查询关联的测试用例列表
        List<TestPlanCaseVO> caseVOs = testPlanCaseMapper.selectAllCasesByPlanIds(Collections.singletonList(id));
        // 4. 填充到 VO 中
        testPlanVO.setCases(caseVOs);
        return testPlanVO;
    }

    @Override
    public List<TestPlanVO> getTestPlanList() {
        // 1. 查询所有测试计划
        List<TestPlanVO> planVOs = PageHelper.queryAllWithVo(testPlanMapper, TestPlanVO.class);
        return fillCasesToPlans(planVOs);
    }

    @Override
    @Transactional
    public void updateTestPlanStatus(Long id, Integer status) {
        TestPlan testPlan = new TestPlan();
        //获取表达式
        String scheduleConfig = testPlanMapper.selectById(id).getScheduleConfig();
        testPlan.setId(id); // 设置ID
        testPlan.setStatus(Status.fromCode(status)); // 设置状态
        testPlanMapper.updateById(testPlan); // 更新测试计划状态
        testPlanCaseMapper.updateStatusByPlanId(id, Status.fromCode(status)); // 更新关联的用例状态
        if (Status.fromCode(status) == Status.NORMAL) {
            // 2. 如果状态为冻结，则删除定时任务
            quartzManager.deleteJob(id);
        } else {
            // 3. 如果状态为启用，则添加定时任务
            if (scheduleConfig != null && !scheduleConfig.isEmpty()) {
                quartzManager.addJob(id, scheduleConfig);
            }
        }
    }

    @Override
    public PageResult<TestPlanVO> getTestPlanPage(Integer page, Integer size, Consumer<LambdaQueryWrapper<TestPlan>> condition) {
        // 1. 分页查询 TestPlanVO 列表
        PageResult<TestPlanVO> testPlanPage = PageHelper.queryWithVo(page, size, testPlanMapper, TestPlanVO.class, condition);
        if (testPlanPage.records().isEmpty()) {
            return testPlanPage;
        }
        // 2. 使用封装方法填充 cases 字段
        List<TestPlanVO> enrichedPlans = fillCasesToPlans(testPlanPage.records());
        // 3. 构造并返回新的 PageResult 实例
        return PageResult.of(
                enrichedPlans,
                testPlanPage.total(),
                testPlanPage.pageNum(),
                testPlanPage.pageSize()
        );
    }

    /**
     * 运行测试计划
     *
     * @param id 测试计划 ID
     */
    @Override
    public void runTestPlan(Long id) throws BaseException {
        setTestPlanRunning(id, true);
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            TestPlan testPlan = testPlanMapper.selectById(id);
            ///0、校验是否存在或者禁用
            if (testPlan == null || testPlan.getStatus() == Status.NORMAL) {
                throw BaseException.of(0, "计划不存在或者已禁用");
            }
            // 1. 获取测试计划详情
            TestPlanVO testPlanVO = getById(id);
            testPlan.setLastExecutionTime(LocalDateTime.now());  // 更新最后一次执行开始时间
            // 2. 获取关联的测试用例列表（已填充）
            List<TestPlanCaseVO> cases = testPlanVO.getCases();  //  获取关联的测试用例列表
            if (cases == null || cases.isEmpty()) {
                throw BaseException.of(0, "该计划没有关联任何测试用例");
            }

            // 3. 按执行顺序排序
            List<Long> caseIds = cases.stream()
                    .sorted(Comparator.comparingInt(TestPlanCaseVO::getExecutionOrder))
                    .map(TestPlanCaseVO::getCaseId)
                    .toList();
            //3.1执行之前，先删除之前的报告缓存数据
            for (Long caseId : caseIds) {
                redisUtils.del(String.format(REDIS_KEY_PATTERN, id, caseId));
            }
            long startTime = System.currentTimeMillis();  //  开始时间
            boolean success = true;             //  执行结果
            // 4. 执行每个用例
            for (Long caseId : caseIds) {
                // 检查是否被用户停止
                if (!isTestPlanRunning(id)) {
                    log.info("测试计划 {} 被用户停止", id);
                    testPlan.setLastExecutionStatus(String.valueOf(ExecStatus.STOPPED)); //  最后一次执行结果
                    success = false;
                    break;   // 停止执行
                }
                String redisKey = String.format(REDIS_KEY_PATTERN, id, caseId);   // redis key
                try {
                    TestPlanExecutor executor = new TestPlanExecutor(testCaseMapper, requestMapper, testPlanCaseMapper, redisUtils, caseId, id);   //  创建执行器
                    executor.execute(caseId, id);
                    redisUtils.hSet(redisKey, "exec_status", ExecStatus.SUCCESS.name());    //  更新执行结果
                } catch (Exception e) {
                    success = false;
                    redisUtils.hSet(redisKey, "exec_status", ExecStatus.FAILED.name());
                    // 新增：记录错误信息到 Redis
                    redisUtils.hSet(redisKey, "error_message", e.getMessage());
                    if (TestPlanExecMode.STOP_ON_FAIL.getValue().equals(testPlan.getExecMode().name())) { // 如果执行模式为停止，则停止执行
                        break;
                    }
                }
            }
            testPlan.setLastExecutionDuration((int) (System.currentTimeMillis() - startTime));//  执行耗时
            // 5. 如果没被中断，标记为成功
            if (success) {
                testPlan.setLastExecutionStatus(String.valueOf(ExecStatus.SUCCESS));   // 最后一次执行结果
            } else {
                testPlan.setLastExecutionStatus(String.valueOf(ExecStatus.FAILED));
            }
            testPlanMapper.updateById(testPlan);
            setTestPlanRunning(id, false);
        });

        try {
            future.get(); // 阻塞等待异步任务完成
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // 保留中断状态
            throw BaseException.of(0, "线程中断", e);
        } catch (ExecutionException e) {
            // 异步任务中抛出的异常会被包装在 ExecutionException 中
            Throwable cause = e.getCause();
            if (cause instanceof BaseException baseException) {
                throw baseException;
            } else {
                throw BaseException.of(0, "执行失败", cause);
            }
        }
    }


    @Override
    public void stopTestPlan(Long planId) {
        if (!isTestPlanRunning(planId)) {
            throw BaseException.of(0, "测试计划未在运行中");
        }
        setTestPlanRunning(planId, false); // 设置停止标志
    }

    @Override
    public List<TestPlan> listByScheduleConfigNotNull() {
        return testPlanMapper.selectList(new LambdaQueryWrapper<TestPlan>()
                .isNotNull(TestPlan::getScheduleConfig)
                .ne(TestPlan::getScheduleConfig, ""));
    }

    @Override
    public Map<String, String> getPlan(Long id) {
        String key = "testPlan_id_" + id + "_testCase_id_";     //计划下每个用例的报告key
        Map<String, String> result = new HashMap<>();   //  创建一个空的结果对象
        //0、校验计划id是否存在
        if (testPlanMapper.selectById(id) == null) {
            throw BaseException.of(0, "计划id不存在");
        }
        //1、需要通过计划id获取对应的caseid， 需要按照排序返回
        List<Long> caseIds = testPlanCaseMapper.selectPlanByCaseIds(id);
        String environmentType = testCaseMapper.getEnvironmentType(caseIds.get(0));
        //2、遍历测试id获取到缓存的请求信息等
        for (Long caseId : caseIds) {
            String caseName = testCaseMapper.getCaseName(caseId);
            Map<String, String> caseMap = redisUtils.getHashEntriesAsStringMap(key + caseId);
            result.put(caseName, caseMap.toString());
        }
        //3、获取到响应提取的字段数据
        Map<String, String> responseExtract = redisUtils.getHashEntriesAsStringMap("testPlan_id_" + id + "_responseExtract_" + environmentType);

        //4、组装进行返回
        result.put("responseExtract", responseExtract.toString());
        return result;
    }


    /**
     * 校验执行顺序是否从 1 开始且连续递增
     */
    private void validateExecutionOrderContinuous(List<TestPlanCaseDTO> cases) {
        List<Integer> orders = cases.stream()
                .map(TestPlanCaseDTO::getExecutionOrder)
                .sorted()
                .toList();

        int expectedOrder = 1;
        for (Integer order : orders) {
            if (order == null || order < 1) {
                throw BaseException.of(0, "执行顺序不能为空或小于1");
            }
            if (order != expectedOrder++) {
                throw BaseException.of(0, "执行顺序不连续，请从1开始依次递增");
            }
        }
    }

    /**
     * 填充每个 TestPlanVO 的 cases 字段
     */
    private List<TestPlanVO> fillCasesToPlans(List<TestPlanVO> planVOs) {
        if (planVOs == null || planVOs.isEmpty()) {
            return planVOs;
        }

        // 1. 提取所有 planId
        List<Long> planIds = planVOs.stream()
                .map(TestPlanVO::getId)
                .toList();

        // 2. 批量查询关联用例
        List<TestPlanCaseVO> caseVOs = testPlanCaseMapper.selectAllCasesByPlanIds(planIds);

        // 3. 按 planId 分组
        Map<Long, List<TestPlanCaseVO>> caseMap = caseVOs.stream()
                .collect(Collectors.groupingBy(TestPlanCaseVO::getPlanId));

        // 4. 填充到 VO 中
        planVOs.forEach(planVO ->
                planVO.setCases(caseMap.getOrDefault(planVO.getId(), Collections.emptyList()))
        );

        return planVOs;
    }


    /**
     * 校验 TestPlanDTO 中的关联用例和执行顺序等信息
     */
    private void validateTestPlanDTO(TestPlanDTO testPlanDTO) {
        List<TestPlanCaseDTO> cases = testPlanDTO.getCases();
        if (cases == null || cases.isEmpty()) {
            throw BaseException.of(0, "关联的用例不能为空");
        } else if (cases.size() > 100) {
            throw BaseException.of(0, "关联的用例不能超过100个");
        }

        // 执行顺序连续性校验
        validateExecutionOrderContinuous(cases);

        HashSet<Integer> orderSet = new HashSet<>();
        cases.forEach(dto -> {
            Long caseId = dto.getCaseId();   // 关联用例ID
            Integer executionOrder = dto.getExecutionOrder();  // 执行顺序
            TestCase testCase = testCaseMapper.selectById(caseId);  // 根据用例ID查询用例信息

            if (testCase == null || testCase.getStatus() == Status.NORMAL) {
                throw BaseException.of(0, "关联的用例不存在或已禁用: " + caseId);
            }

            if (executionOrder == null || executionOrder < 1) {
                throw BaseException.of(0, "执行顺序不能为空或小于1: " + caseId);
            }

            if (!orderSet.add(executionOrder)) {
                throw BaseException.of(0, "执行顺序不能重复: " + executionOrder);
            }
        });
    }


    /**
     * 设置测试计划为“运行中”
     */
    public void setTestPlanRunning(Long planId, boolean running) {
        String key = TEST_PLAN_RUNNING_KEY + planId;
        if (running) {
            // 使用 redisUtils.setex 设置带过期时间的值
            redisUtils.set(key, "1");
            redisUtils.expire(key, 5 * 60); // 设置过期时间为5分钟
        } else {
            redisUtils.del(key);
        }
    }

    /**
     * 查询测试计划是否正在运行
     */
    public boolean isTestPlanRunning(Long planId) {
        String key = TEST_PLAN_RUNNING_KEY + planId;
        return redisUtils.exists(key);
    }
}
